Type CalAbstractMixer Abstract
	
		
	Method New()
	
	End Method
	
	'Method (model:CalModel)
	
	'End
	
	Method isDefaultMixer:Byte()
		Return False
	End Method
	
	Method updateAnimation(deltaTime:Float)
		DebugLog "This is the abstract mixer! Use one of the extended Typees!"
	End Method
	
	Method updateSkeleton()
		DebugLog "This is the abstract mixer! Use one of the extended Typees!"
	End Method
	
	
End Type

Type CalMixer Extends CalAbstractMixer
	
	Field m_model:CalModel
	Field m_vectorAnimation:CalAnimation[]
	Field m_listAnimationAction:CalAnimationAction[]
	Field m_listAnimationCycle:CalAnimationCycle[]
	Field m_animationTime:Float
	Field m_animationDuration:Float
	Field m_timeFactor:Float
	
	Function FromModel:CalMixer(model:CalModel)
		Local mix:CalMixer = New CalMixer
		mix.m_model = model
		mix.m_vectorAnimation = New CalAnimation[model.getCoreModel().getCoreAnimationCount()]
		mix.m_listAnimationAction = New CalAnimationAction[0]
		mix.m_listAnimationCycle = New CalAnimationCycle[0]
		mix.m_animationTime = 0
		mix.m_animationDuration = 0
		mix.m_timeFactor = 1
		
		Return mix
		
	End Function
	
	Method isDefaultMixer:Byte()
		Return True
	End Method
	
	Method blendCycle:Byte(id:Int, weight:Float, Delay:Float)
	
		If id < 0 Or id >= m_vectorAnimation.length Then
			DebugLog "Invalid Animation ID"
			Return False
		EndIf
	
		' get the animation for the given id
		Local animation:CalAnimation = m_vectorAnimation[id]
	
		' create a new animation instance if it is not active yet
		If animation = Null Then
			' take the fast way out if we are trying to clear an inactive animation
			If weight = 0 Then
				Return True
			EndIf
			' get the core animation
			Local coreAnimation:CalCoreAnimation = m_model.getCoreModel().getCoreAnimation(id)
			If coreAnimation = Null Then
				Return False
			EndIf
			' ensure that the animation's first and last key frame match for proper looping
			addExtraKeyframeForLoopedAnim(coreAnimation)
	
			' create a new animation cycle instance
			Local animationCycle:CalAnimationCycle = CalAnimationCycle.BuildFromCore(coreAnimation)
	
			' insert the new animation into the tables
			m_vectorAnimation[id] = animationCycle
			Local lac:CalAnimationCycle[] = New CalAnimationCycle[m_listAnimationCycle.length+1]
			For Local i:Int = 1 To m_listAnimationCycle.length
				lac[i] = m_listAnimationCycle[i-1]
			Next
			lac[0] = animationCycle
			m_listAnimationCycle = lac
	
			' blend the animation
			Return animationCycle.blend(weight, Delay)
		EndIf
	
		' check if this is really an animation cycle instance
		If animation.getType() <> CalAnimationType.TYPE_CYCLE Then
			DebugLog "Not Animation Cycle Type"
			Return False
		EndIf
	
		' clear the animation cycle from the active vector if the target weight is zero
		If weight = 0 Then
			m_vectorAnimation[id] = Null
		EndIf
	
		' cast it to an animation cycle
		animationCycle = CalAnimationCycle(animation)
	
		' blend the animation cycle
		animationCycle.blend(weight, Delay)
		animationCycle.checkCallbacks(0, m_model)
	
		Return True		
		
	End Method
	
	Method clearCycle:Byte(id:Int, Delay:Float)
		' check if the animation id is valid
		If id < 0 Or id >= m_vectorAnimation.length Then
			DebugLog "Invalid Animation ID"
			Return False
		EndIf
	
		' get the animation for the given id
		Local animation:CalAnimation = m_vectorAnimation[id]
	
		' we can only clear cycles that are active
		If animation = Null Then
			Return True
		EndIf
		' check if this is really a animation cycle instance
		If animation.getType() <> CalAnimationType.TYPE_CYCLE Then
			DebugLog "Invalid Animation Type"
			Return False
		EndIf
	
		' clear the animation cycle from the active vector
		m_vectorAnimation[id] = Null
	
		' cast it to an animation cycle
		Local animationCycle:CalAnimationCycle = CalAnimationCycle(animation)
	
		' set animation cycle to async state
		animationCycle.setAsync(m_animationTime, m_animationDuration)
	
		' blend the animation cycle
		animationCycle.blend(0, Delay)
		animationCycle.checkCallbacks(0, m_model)
	
		Return True	
	
	End Method
	
	
	Method executeAction:Byte(id:Int, delayIn:Float, delayOut:Float, weightTarget:Float, autoLock:Byte)
		
		' get the core animation
		Local coreAnimation:CalCoreAnimation = m_model.getCoreModel().getCoreAnimation(id)
		
		If coreAnimation = Null Then
			Return False
		EndIf
		
		' create a new animation action instance
		Local animationAction:CalAnimationAction = CalAnimationAction.BuildFromCore(coreAnimation)
	
		' insert the new animation into the table
		Local laa:CalAnimationAction[] = New CalAnimationAction[m_listAnimationAction.length+1]
		
		For Local i:Int = 1 To m_listAnimationAction.length
			laa[i] = m_listAnimationAction[i-1]
		Next
		laa[0] = animationAction
		m_listAnimationAction = laa
	
		' execute the animation
		animationAction.execute(delayIn, delayOut, weightTarget, autoLock)
		animationAction.checkCallbacks(0, m_model)
	
		Return True		
		
	End Method
	
	Method removeAction:Byte(id:Int)

		' get the core animation
		Local coreAnimation:CalCoreAnimation = m_model.getCoreModel().getCoreAnimation(id)
		If coreAnimation = Null Then
			Return False
		EndIf
		
		' update all active animation actions of this model
		Local animationActionId:Int = 0
		While animationActionId < m_listAnimationAction.length
			' find the specified action and remove it
			If m_listAnimationAction[animationActionId].getCoreAnimation() = coreAnimation Then
				' found, so remove
				m_listAnimationAction[animationActionId].completeCallbacks(m_model)
				Local laa:CalAnimationAction[] = New CalAnimationAction[m_listAnimationAction.length-1]
				Local pos:Int=0
				For Local i:Int = 0 To m_listAnimationAction.length-1
					If i <> animationActionId Then
						laa[pos] = m_listAnimationAction[i]
						pos=pos+1
					EndIf
				Next
				m_listAnimationAction = laa
				'm_listAnimationAction.splice(animationActionId, 1)
				Return True
			EndIf
	
			animationActionId=animationActionId+1
		Wend
	
		Return False		

	End Method
	
	
	Method updateAnimation(deltaTime:Float)
	
	
		' update the current animation time
		If m_animationDuration = 0 Then
			m_animationTime = 0		
		Else
			m_animationTime = m_animationTime+deltaTime * m_timeFactor
			
			If m_animationTime >= m_animationDuration Or m_animationTime < 0 Then
				m_animationTime = (m_animationTime Mod m_animationDuration) 'use Modf?
			EndIf
			
			If m_animationTime < 0 Then
				m_animationTime = m_animationTime + m_animationDuration
			EndIf
		EndIf
	
		' update all active animation actions of this model
		Local animationActionId:Int = 0
		
		While animationActionId < m_listAnimationAction.length
		
			Local animationAction:CalAnimationAction = m_listAnimationAction[animationActionId]
			' update and check if animation action is still active
			If animationAction.update(deltaTime) Then
				animationAction.checkCallbacks(animationAction.getTime(), m_model)
				animationActionId = animationActionId + 1
			Else
				' animation action has ended, destroy and remove it from the animation list
				animationAction.completeCallbacks(m_model)
				Local laa:CalAnimationAction[] = New CalAnimationAction[m_listAnimationAction.length-1]
				Local pos:Int = 0
				For Local i:Int = 0 To m_listAnimationAction.Length-1
					If i <> animationActionId Then
						laa[pos] = m_listAnimationAction[i]
						pos = pos + 1
					EndIf
				Next
				m_listAnimationAction = laa
			EndIf
		Wend
	
		' TODO: update all active animation poses of this model
		' ...
	
		' update the weight of all active animation cycles of this model
		Local accumulatedWeight:Float = 0
		Local accumulatedDuration:Float = 0
		Local animationCycleId:Int = 0
		While animationCycleId < m_listAnimationCycle.length
			Local animationCycle:CalAnimationCycle = m_listAnimationCycle[animationCycleId]
			' update and check if animation cycle is still active
			If animationCycle.update(deltaTime) = True Then
				' check if it is in sync. if yes, update accumulated weight and duration
				If animationCycle.getState() = CalAnimationState.STATE_SYNC Then
					accumulatedWeight = accumulatedWeight+animationCycle.getWeight()
					accumulatedDuration = accumulatedDuration+animationCycle.getWeight() * animationCycle.getCoreAnimation().getDuration()
				EndIf
	
				animationCycle.checkCallbacks(m_animationTime, m_model)
				animationCycleId=animationCycleId+1
			
			Else
				' animation cycle has ended, destroy and remove it from the animation list
				animationCycle.completeCallbacks(m_model)
				Local lac:CalAnimationCycle[] = New CalAnimationCycle[m_listAnimationCycle.length-1]
				pos = 0
				For i = 0 To m_listAnimationCycle.length-1
					If i <> animationCycleId Then
						lac[pos] = m_listAnimationCycle[i]
						pos=pos+1
					EndIf
				Next
				m_listAnimationCycle = lac
			EndIf
		Wend
	
		' adjust the global animation cycle duration
		If accumulatedWeight > 0 Then
			m_animationDuration = accumulatedDuration / accumulatedWeight
		Else
			m_animationDuration = 0
		EndIf
	
	
	End Method
	
	
	Method updateSkeleton()
		' get the skeleton we need to update
		Local skeleton:CalSkeleton = m_model.getSkeleton()
		If skeleton = Null Then 
			Return
		EndIf
		' clear the skeleton state
		skeleton.clearState()
	
		' get the bone vector of the skeleton
		Local vectorBone:CalBone[] = skeleton.getVectorBone()
	
		Local translation:CalVector = New CalVector
		Local rotation:CalQuaternion = New CalQuaternion
	
		' loop through all animation actions
		For Local animationActionId:Int = 0 To m_listAnimationAction.length-1
			Local animationAction:CalAnimationAction = m_listAnimationAction[animationActionId]
			
			' get the core animation instance
			Local coreAnimation:CalCoreAnimation = animationAction.getCoreAnimation()
	
			' get the list of core tracks of above core animation
			Local listCoreTrack:CalCoreTrack[] = coreAnimation.getListCoreTrack()
	
			' loop through all core tracks of the core animation
			For Local coreTrackId:Int = 0 To listCoreTrack.length-1
				' get the appropriate bone of the track
				Local bone:CalBone = vectorBone[ listCoreTrack[coreTrackId].getCoreBoneId() ]
	
				' get the current translation and rotation
				listCoreTrack[coreTrackId].getState(animationAction.getTime(), translation, rotation)
	
				' blend the bone state with the new state
				bone.blendState(animationAction.getWeight(), translation, rotation)
			Next
		Next
	
		' lock the skeleton state
		skeleton.lockState()
	
		' loop through all animation cycles
		For Local animationCycleId:Int = 0 To m_listAnimationCycle.length-1
			Local animationCycle:CalAnimationCycle = m_listAnimationCycle[animationCycleId]
	
			' get the core animation instance
			coreAnimation = animationCycle.getCoreAnimation()
	
			' calculate adjusted time
			Local animationTime:Float
			If animationCycle.getState() = CalAnimationState.STATE_SYNC Then
				If m_animationDuration = 0 Then
					animationTime = 0
				Else
					animationTime = m_animationTime * coreAnimation.getDuration() / m_animationDuration
				EndIf
			Else
				animationTime = animationCycle.getTime()
			EndIf
			' get the list of core tracks of above core animation
			listCoreTrack = coreAnimation.getListCoreTrack()
	
			' loop through all core tracks of the core animation
			For coreTrackId = 0 To listCoreTrack.length-1
				' get the appropriate bone of the track
				bone = vectorBone[ listCoreTrack[coreTrackId].getCoreBoneId() ]
	
				' get the current translation and rotation
				listCoreTrack[coreTrackId].getState(animationTime, translation, rotation)
	
				' blend the bone state with the new state
				bone.blendState(animationCycle.getWeight(), translation, rotation)
			Next
		Next
	
		' lock the skeleton state
		skeleton.lockState()
	
		' let the skeleton calculate its final state
		skeleton.calculateState()		
	End Method
	
	
	Method getAnimationTime:Float()
		Return m_animationTime
	End Method
		
	Method getAnimationDuration:Float()
		Return m_animationDuration
	End	Method
	
	Method setAnimationTime(animationTime:Float)
		m_animationTime = animationTime
	End Method
	
	Method setTimeFactor(timeFactor:Float)
		m_timeFactor = timeFactor
	End Method
	
	Method getTimeFactor:Float()
		Return m_timeFactor
	End Method
	
	Method getCalModel:CalModel()
		Return m_model
	End Method
	
	Method getAnimationVector:CalAnimation[]()
		Return m_vectorAnimation
	End Method
	
	Method getAnimationActionList:CalAnimationAction[]()
		Return m_listAnimationAction
	End Method
	
	Method getAnimationCycle:CalAnimationCycle[]()
		Return m_listAnimationCycle
	End Method
	
	
		
End Type


Function addExtraKeyframeForLoopedAnim(coreAnimation:CalCoreAnimation)

	Local listCoreTrack:CalCoreTrack[] = coreAnimation.getListCoreTrack()
	If listCoreTrack.length = 0 Then
		Return
	EndIf
	
	Local coreTrackId:Int = 0
	If listCoreTrack[coreTrackId] = Null Then
		Return
	EndIf
	Local lastKeyframe:CalCoreKeyframe = listCoreTrack[coreTrackId].getCoreKeyframe( listCoreTrack[coreTrackId].getCoreKeyframeCount() - 1 )
	If lastKeyframe = Null Then
		Return
	EndIf
	If lastKeyframe.getTime() < coreAnimation.getDuration() Then
		For coreTrackId = 0 To listCoreTrack.length-1
			Local coreTrack:CalCoreTrack = listCoreTrack[coreTrackId]

			Local firstKeyframe:CalCoreKeyframe = coreTrack.getCoreKeyframe(0)
			Local newKeyframe:CalCoreKeyframe = New CalCoreKeyframe

			newKeyframe.setTranslation(firstKeyframe.getTranslation())
			newKeyframe.SetRotation(firstKeyframe.GetRotation())
			newKeyframe.setTime(coreAnimation.getDuration())

			coreTrack.addCoreKeyframe(newKeyframe)
		Next
	EndIf	

End Function
